﻿using System;
using System.Collections.Generic;
using System.Text;
using HBDotNet;
using System.Timers;
using System.Windows.Forms;
using System.Xml;
using System.Net;
using System.IO;

namespace Weather
{
    [HBDevice("Weather")]
    public class Weather : HBDeviceBase
    {
        System.Timers.Timer timer = new System.Timers.Timer();
        string LocationName = "";
        string LocationId = "";
        string PartnerID = "1017077486";
        string LicenseKey = "9e1fbc5c18e643cc";
        int Interval = 0;

        public override bool OnInit(bool enabled)
        {
            if (enabled)
            {
                enabled = OnEnable();
            }
            base.OnInit(enabled);
            return true;
        }
        public override void OnShutdown()
        {
            // Stop the weather timer
            timer.Enabled = false;
            base.OnShutdown();
        }
        public override bool OnEnable()
        {            
            //Setup the timer
            timer.Interval = 3000;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Enabled = true;
            return true;
        }
        public override bool OnDisable()
        {
            // Stop the weather timer
            timer.Enabled = false;
            return base.OnDisable();
        }
        public override void OnAboutBox()
        {
            MessageBox.Show("This plugin returns weather information from weather.com website.");
        }
        protected override bool OnPropertyChangeRequest(HBDotNet.HBProperty hbProperty, string newValue)
        {
            if (hbProperty.Name == "Location Name" || hbProperty.Name == "Unit")
            {
                UpdateWeather();
            }

            return base.OnPropertyChangeRequest(hbProperty, newValue);
        }
        /// <summary>
        /// On install registers following properties
        /// </summary>
        /// <returns></returns>
        public override bool OnRegisterProperties()
        {
            // Weather.com account information
            //Properties.Add(new HBProperty("Partner ID", "Partner ID", false, "", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptAlpha, "Enter the partner id.  This is available from weather.com for free."));
            //Properties.Add(new HBProperty("License Key", "License Key", false, "", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptAlpha, "Enter the partner id.  This is available from weather.com for free."));
            Properties.Add(new HBProperty("Location Name", "Location Name", false, "London, United Kingdom", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptAlpha, "Enter location (i.e. London, United Kingdom)"));
            Properties.Add(new HBProperty("Image Location", "Image Location", false, "C:\\Program Files\\HouseBot\\Config\\Themes\\weather\\icons\\93x93", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Unit", "Unit", false, "Metric", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptAlpha, "Enter Metric or Standard."));
            HBModule.CreateDevicePropertyValue("Unit", "Metric");
            HBModule.CreateDevicePropertyValue("Unit", "Imperial");
            Properties.Add(new HBProperty("Interval", "Interval", false, "300", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptNumeric, ""));
            // Readonly  weather stats
            Properties.Add(new HBProperty("Observation Station Name", "Observation Station Name", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Time and Date of Observation", "Current Time and Date of Observation", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Temperature", "Current Temperature", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Feels Like Temperature", "Current Feels Like Temperature", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Weather Summary", "Current Weather Summary", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Weather Icon", "Current Weather Icon", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Barometric Pressure", "Current Barometric Pressure", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Barometric Trend", "Current Barometric Trend", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Wind Speed", "Current Wind Speed", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Wind Gust", "Current Wind Gust", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Wind Direction", "Current Wind Direction", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Relative Humidity", "Current Relative Humidity", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Visibility", "Current Visibility", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current UV Index", "Current UV Index", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Dew point", "Current Dew point", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Sunrise", "Current Sunrise", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Sunset", "Current Sunset", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Moon Phase", "Current Moon Phase", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Current Moon Icon", "Current Moon Icon", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            // Next five days
            for (int d = 0; d < 5; d++)
            {
                // Full day
                Properties.Add(new HBProperty("Forcast " + d + " Day of Week", "Forcast " + d + " Day of Week", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Date", "Forcast " + d + " Date", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " High Temperature", "Forcast " + d + " High Temperature", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Low Temperature", "Forcast " + d + " Low Temperature", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Sunrise", "Forcast " + d + " Sunrise", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Sunset", "Forcast " + d + " Sunset", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                // Day time
                Properties.Add(new HBProperty("Forcast " + d + " Day Icon", "Forcast " + d + " Day Icon", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Day Summary", "Forcast " + d + " Day Summary", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Day Wind Speed", "Forcast " + d + " Day Wind Speed", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Day Wind Gust", "Forcast " + d + " Day Wind Gust", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Day Wind Direction", "Forcast " + d + " Day Wind Direction", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Day Chance of Rain", "Forcast " + d + " Day Chance of Rain", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Day Humidity", "Forcast " + d + " Day Humidity", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                // Night time
                Properties.Add(new HBProperty("Forcast " + d + " Night Icon", "Forcast " + d + " Night Icon", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Night Summary", "Forcast " + d + " Night Summary", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Night Wind Speed", "Forcast " + d + " Night Wind Speed", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Night Wind Gust", "Forcast " + d + " Night Wind Gust", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Night Wind Direction", "Forcast " + d + " Night Wind Direction", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Night Chance of Rain", "Forcast " + d + " Night Chance of Rain", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
                Properties.Add(new HBProperty("Forcast " + d + " Night Humidity", "Forcast " + d + " Night Humidity", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            }
            return base.OnRegisterProperties();
        }
        public void SetProperty(string propertyName, string propertyValue)
        {
            Properties.GetPropertyByName(propertyName).CurrentValue = propertyValue;
        }
        public void SetProperty(string propertyName, double propertyValue)
        {
            Properties.GetPropertyByName(propertyName).CurrentValue = propertyValue.ToString();
        }
        public void SetProperty(string propertyName, double propertyValue, int decimalPrecision)
        {
            Properties.GetPropertyByName(propertyName).CurrentValue = propertyValue.ToString("F" + decimalPrecision.ToString());
        }
        public string GetProperty(string propertyName)
        {
            return Properties.GetPropertyByName(propertyName).CurrentValue;
        }
        /// <summary>
        /// Method is run on tick of timer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            //Set interval
            if (!int.TryParse(GetProperty("Interval"), out Interval))
            {
                Interval = 0;
            }
            //If there was a problem then reset to 5 mins
            if (Interval < 1)
            {
                HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, "Interval setting is in wrong format.  Has been reset to 5 mins.");
                timer.Interval = 300000;
                SetProperty("Interval", 300);
            }
            else
            {
                timer.Interval = Interval * 1000;
            }

            UpdateWeather();
        }
        /// <summary>
        /// Method gets weather information from weather.com
        /// </summary>
        private void UpdateWeather()
        {
            XmlDocument xDoc = new XmlDocument();
            
            //Get location if changed or blank
            if (!LocationName.Equals(GetProperty("Location Name")))
            {                
                xDoc.LoadXml(GetRssFeed("http://xoap.weather.com/search/search?where=" + GetProperty("Location Name")));
                string locationName = GetValue(xDoc, "/search/loc");
                if (!locationName.Trim().Equals(String.Empty))
                {
                    UpdateProperty("Location Name", locationName);
                    LocationId = GetValue(xDoc, "/search/loc/@id");
                }
            } 

            //set correct unit
            string u = "s";
            if (GetProperty("Unit").Equals("Metric")) u = "m";
            //get xml
            xDoc.LoadXml(GetRssFeed("http://xoap.weather.com/weather/local/" + LocationId + "?cc=*&dayf=5&link=xoap&prod=xoap&par=" + PartnerID + "&key=" + LicenseKey + "&unit=" + u));

            if (GetValue(xDoc, "/error/err", "OK").Equals("OK"))
            {
                ParseXML(xDoc);
            }
            // There was a problem so prompt it
            else
            {
                HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, GetValue(xDoc, "/error/err"));
            }
        }
        /// <summary>
        /// Parses weather.com XML
        /// </summary>
        /// <param name="xDoc"></param>
        private void ParseXML(XmlDocument xDoc)
        {
            try
            {
                //reformat icon location if required
                string sIconLocation = GetProperty("Image Location");
                if (!sIconLocation.EndsWith("\\"))
                    SetProperty("Image Location", sIconLocation + "\\");
                // Units
                string sUT = "°" + GetValue(xDoc, "/weather/head/ut");
                string sUD = " " + GetValue(xDoc, "/weather/head/ud");
                string sUS = " " + GetValue(xDoc, "/weather/head/us");
                string sUP = " " + GetValue(xDoc, "/weather/head/up");
                // Current Conditions
                UpdateProperty("Observation Station Name", GetValue(xDoc, "/weather/cc/obst"));
                UpdateProperty("Current Time and Date of Observation", GetValue(xDoc, "/weather/cc/lsup"));
                UpdateProperty("Current Temperature", AddUnit(GetValue(xDoc, "/weather/cc/tmp"), sUT));
                UpdateProperty("Current Feels Like Temperature", AddUnit(GetValue(xDoc, "/weather/cc/flik"), sUT));
                UpdateProperty("Current Weather Summary", GetValue(xDoc, "/weather/cc/t"));
                UpdateProperty("Current Weather Icon", sIconLocation + FormatImage(GetValue(xDoc, "/weather/cc/icon")));
                UpdateProperty("Current Barometric Pressure", AddUnit(GetValue(xDoc, "/weather/cc/bar/r"), sUP));
                UpdateProperty("Current Barometric Trend", GetValue(xDoc, "/weather/cc/bar/d"));
                UpdateProperty("Current Wind Speed", AddUnit(GetValue(xDoc, "/weather/cc/wind/s"), sUS));
                UpdateProperty("Current Wind Gust", AddUnit(GetValue(xDoc, "/weather/cc/wind/gust"), sUS));
                UpdateProperty("Current Wind Direction", GetValue(xDoc, "/weather/cc/wind/t"));
                UpdateProperty("Current Relative Humidity", AddUnit(GetValue(xDoc, "/weather/cc/hmid"), " %"));
                UpdateProperty("Current Visibility", GetValue(xDoc, "/weather/cc/vis") + sUD);
                UpdateProperty("Current UV Index", GetValue(xDoc, "/weather/cc/uv/i") + " " + GetValue(xDoc, "/weather/cc/uv/t").Replace("N/A", string.Empty));
                UpdateProperty("Current Dew point", AddUnit(GetValue(xDoc, "/weather/cc/dewp"), sUT));
                UpdateProperty("Current Sunrise", GetValue(xDoc, "/weather/loc/sunr"));
                UpdateProperty("Current Sunset", GetValue(xDoc, "/weather/loc/suns"));
                UpdateProperty("Current Moon Phase", GetValue(xDoc, "/weather/cc/moon/t"));
                UpdateProperty("Current Moon Icon", sIconLocation + FormatImage(GetValue(xDoc, "/weather/cc/moon/icon")));
                // Next five days
                for (int d = 0; d < 5; d++)
                {
                    //Full day
                    UpdateProperty("Forcast " + d + " Day of Week", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/@t"));
                    UpdateProperty("Forcast " + d + " Date", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/@dt"));
                    UpdateProperty("Forcast " + d + " High Temperature", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/hi"), sUT));
                    UpdateProperty("Forcast " + d + " Low Temperature", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/low"), sUT));
                    UpdateProperty("Forcast " + d + " Sunrise", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/sunr"));
                    UpdateProperty("Forcast " + d + " Sunset", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/suns"));
                    // Day time
                    UpdateProperty("Forcast " + d + " Day Icon", sIconLocation + FormatImage(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/icon")));
                    UpdateProperty("Forcast " + d + " Day Summary", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/t"));
                    UpdateProperty("Forcast " + d + " Day Wind Speed", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/wind/s"), sUS));
                    UpdateProperty("Forcast " + d + " Day Wind Gust", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/wind/gust"), sUS));
                    UpdateProperty("Forcast " + d + " Day Wind Direction", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/wind/t"));
                    UpdateProperty("Forcast " + d + " Day Chance of Rain", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/ppcp"), " %"));
                    UpdateProperty("Forcast " + d + " Day Humidity", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='d']/hmid"), " %"));
                    //Night Time    
                    UpdateProperty("Forcast " + d + " Night Icon", sIconLocation + FormatImage(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/icon")));
                    UpdateProperty("Forcast " + d + " Night Summary", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/t"));
                    UpdateProperty("Forcast " + d + " Night Wind Speed", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/wind/s"), sUS));
                    UpdateProperty("Forcast " + d + " Night Wind Gust", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/wind/gust"), sUS));
                    UpdateProperty("Forcast " + d + " Night Wind Direction", GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/wind/t"));
                    UpdateProperty("Forcast " + d + " Night Chance of Rain", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/ppcp"), " %"));
                    UpdateProperty("Forcast " + d + " Night Humidity", AddUnit(GetValue(xDoc, "/weather/dayf/day[@d='" + d + "']/part[@p='n']/hmid"), " %"));
                }
            }
            catch (Exception e)
            {
                HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, "Error parsing XML: " + e.Message);
            }
        }

        /// <summary>
        /// Adds unit to the strings
        /// </summary>
        /// <param name="value"></param>
        /// <param name="unit"></param>
        /// <returns></returns>
        private string AddUnit(string value, string unit)
        {
            if (value.Trim().Equals(string.Empty) ||
                value.ToLower().Trim().Equals("n/a"))
            {
                return value;
            }
            else
            {
                return value + unit;
            }
        }
        /// <summary>
        /// Format for weather.com icon image
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private string FormatImage(string value)
        {
            if (value.Length.Equals(1))
            {
                return "0"+value+".png";
            }
            else
            {
                return value+".png";
            }
        }
        /// <summary>
        /// Returns rss feed as a string
        /// </summary>
        /// <param name="url">http url of rss feed</param>
        /// <returns>xml as string</returns>
        private string GetRssFeed(string url)
        {
            try
            {

                WebRequest request = WebRequest.Create(url);

                //Uncomment code if proxy is required
                //WebProxy proxy = new WebProxy("http://ip:port/", true);
                //proxy.Credentials = 
                //    new NetworkCredential( 
                //        "username", 
                //        "password", 
                //        "domain");
                //request.Proxy = proxy;

                // Send the 'HttpWebRequest' and wait for response. 
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        Encoding ec = Encoding.GetEncoding("utf-8");

                        using (StreamReader reader = new StreamReader(stream, ec))
                        {
                            return reader.ReadToEnd();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, "Error with feed: " + e.Message);
                
                return  "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?> " +
                        "<error>" +
                        "<err type=\"xxx\">Unable to connect to Weather.com</err>" +
                        "</error>";
            }
        }     
        /// <summary>
        /// Parses Xml using xpath and returns inner xml
        /// </summary>
        /// <param name="xDoc">XmlDocument to parse</param>
        /// <param name="xpath">XPath statement to use</param>
        /// <returns>InnerXml string</returns>
        private string GetValue(XmlDocument xDoc, string xpath)
        {
            return GetValue(xDoc, xpath, string.Empty);
        }
        /// <summary>
        /// Parses Xml using xpath and returns inner xml
        /// </summary>
        /// <param name="xDoc">XmlDocument to parse</param>
        /// <param name="xpath">XPath statement to use</param>
        /// <param name="returnValue">String that will be returned if null or error</param>
        /// <returns>InnerXml string</returns>
        private string GetValue(XmlDocument xDoc, string xpath, string returnValue)
        {
            try
            {
                return xDoc.SelectSingleNode(xpath).InnerXml;
            }
            catch
            {
                return returnValue;
            }
        }
        /// <summary>
        /// Sends message to UI, update only if changed
        /// </summary>
        /// <param name="deviceName"></param>
        /// <param name="value"></param>
        private void UpdateProperty(string deviceName, string value)
        {
            if(!GetProperty(deviceName).Equals(value))
                SetProperty(deviceName, value);
        }
        /// <summary>
        /// Returns weather.com errors
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private string ReturnErrorDescription(string value)
        {
            switch (value)
            {
                case "1":
                    return "No Location Provided";
                case "2":
                    return "Invalid Location";
                case "100":
                    return "Invalid Partner Id";
                case "101":
                    return "Invalid Product Code";
                case "102":
                    return "Invalid License Key";
                default:
                    return "An Unknown Error has occurred";
            }
        }
    }
}